package com.safe.first.infrastructure.interceptor;

import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;
import com.safe.first.infrastructure.constants.GrantType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

/**
 * @author Mickey
 * @since 2022/6/12
 **/
@Component
public class CompositeTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private final ObjectMapper objectMapper;

    private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login",
            "POST");


    public CompositeTokenAuthenticationFilter(AuthenticationManager authenticationManager,
                                              AuthenticationSuccessHandler authenticationSuccessHandler,
                                              ObjectMapper objectMapper,
                                              MySimpleUrlAuthenticationFailureHandler mySimpleUrlAuthenticationFailureHandler) {
        super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
        setAuthenticationSuccessHandler(authenticationSuccessHandler);
        setAuthenticationFailureHandler(mySimpleUrlAuthenticationFailureHandler);
        this.objectMapper = objectMapper;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
    }

    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        if (!requiresAuthentication(request, response)) {
            chain.doFilter(request, response);
            return;
        }
        Map<String, String> params;
        try (ServletInputStream inputStream = request.getInputStream()) {
            params = objectMapper.readValue(inputStream, new TypeReference<>() {
            });
        } catch (IOException e) {
            throw new BadCredentialsException("incorrect parameter");
        }
        try {
            String grantType = params.get("grantType");
            if (Strings.isNullOrEmpty(grantType)) {
                throw new AuthenticationServiceException("grantType not null");
            }
            MyAbstractAuthenticationProcessingFilter granter = granter(grantType, request, response);
            Authentication authenticationResult = granter.attemptAuthentication(params, request, response);
            if (authenticationResult == null) {
                // return immediately as subclass has indicated that it hasn't completed
                return;
            }
            granter.getSessionAuthenticationStrategy().onAuthentication(authenticationResult, request, response);
            // Authentication success
            if (granter.getContinueChainBeforeSuccessfulAuthentication()) {
                chain.doFilter(request, response);
            }
            granter.successfulAuthentication(request, response, chain, authenticationResult);
        }
        catch (InternalAuthenticationServiceException failed) {
            this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
            unsuccessfulAuthentication(request, response, failed);
        }
        catch (AuthenticationException ex) {
            // Authentication failed
            unsuccessfulAuthentication(request, response, ex);
        }
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        return null;
    }

    public MyAbstractAuthenticationProcessingFilter granter(String grantType, HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, ServletException, IOException {
        return SpringUtil.getBean(GrantType.of(grantType).getName());
    }
}
